package com.newhero.security;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;

public class RSAUtil  implements Serializable
{
    private static final long serialVersionUID = 2425971237592505266L;
    private static RSAUtil rsa = new RSAUtil();
    /** 指定加密算法为RSA */
    private static String ALGORITHM = "RSA";
    /**
     * 公钥文件前缀
     */
    private static String KEY_RSA_PUB = "Pub";
    /**
     * 私钥文件前缀
     */
    private static String KEY_RSA_OWN = "Pri";
    /** 指定key的大小 */
    private static int KEYSIZE = 1024;
    
    /** 
     * 加密/解密算法/工作模式/填充方式 
     * */  
    private static final String CIPHER_ALGORITHM="RSA/ECB/PKCS5Padding";  
    
    private RSAUtil()
    {
        
    }
    
    public static RSAUtil getInstance()
    {
        return rsa;
    }
    
    public boolean createRSAKeys(String filePath,String fileName)
    {       
        try 
        {
            KeyPair keyPair = generateKeyPair();
            RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();

            byte[] pubModBytes = pubKey.getModulus().toByteArray();
            byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray();
            byte[] priModBytes = priKey.getModulus().toByteArray();
            byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray();
            //生成密钥   
            RSAPublicKey recoveryPubKey = generateRSAPublicKey(pubModBytes, pubPubExpBytes);
            RSAPrivateKey recoveryPriKey = generateRSAPrivateKey(priModBytes, priPriExpBytes);
            FileOutputStream fosPub = new FileOutputStream(filePath+KEY_RSA_PUB+fileName);
            ObjectOutputStream oosPub = new ObjectOutputStream(fosPub);
            FileOutputStream fosOwn = new FileOutputStream(filePath+KEY_RSA_OWN+fileName);
            ObjectOutputStream oosOwn = new ObjectOutputStream(fosOwn);
            
            oosPub.writeObject(recoveryPubKey);
            oosOwn.writeObject(recoveryPriKey);
            oosPub.flush();
            oosOwn.flush();
            oosPub.close();
            oosOwn.close();
            return true;
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("生成RSA密钥失败！");
            return false;
        }
    }
    
    /**
     * 生成密钥对 　　
     * 
     * @return KeyPair 　　
     * @throws NoSuchAlgorithmException 
     * @throws Exception
     *             　　
     */
    private KeyPair generateKeyPair() throws NoSuchAlgorithmException 
    {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM,new org.bouncycastle.jce.provider.BouncyCastleProvider());
        keyPairGen.initialize(KEYSIZE, new SecureRandom());
        KeyPair keyPair = keyPairGen.genKeyPair();
        return keyPair;
    }

    /**
     * 生成公钥 　　
     * 
     * @param modulus
     *            　　
     * @param publicExponent
     *            　　
     * @return RSAPublicKey　　
     */
    public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) 
    {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance(ALGORITHM,new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        try {
            return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        } catch (InvalidKeySpecException ex) {
            return null;
        }
    }

    /**
     * 生成私钥 　　
     * 
     * @param modulus
     *            　　
     * @param privateExponent
     *            　　
     * @return RSAPrivateKey 　　
     */
    public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus,byte[] privateExponent) 
    {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance(ALGORITHM,new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) 
        {
            
        }

        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        try 
        {
            return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
        } catch (InvalidKeySpecException ex) 
        {
            return null;
        }
    }

    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    @SuppressWarnings("unused")
	private RSAPublicKey getPubKeyFromFile(String filePath)
    {       
        RSAPublicKey pubKey = null;
        try {
            InputStream is = new FileInputStream(filePath);;
            ObjectInputStream oos = new ObjectInputStream(is);
            pubKey = (RSAPublicKey) oos.readObject();
            oos.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("通过公钥文件获取密钥KEY失败！");
            return null;
        }
        return pubKey;
    }
    
    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    @SuppressWarnings("unused")
	private RSAPrivateKey getOwnKeyFromFile(String filePath)
    {       
        RSAPrivateKey ownKey = null;
        try {
            InputStream is = new FileInputStream(filePath);;
            ObjectInputStream oos = new ObjectInputStream(is);
            ownKey = (RSAPrivateKey) oos.readObject();
            oos.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("通过私钥文件获取密钥KEY失败！");
            return null;
        }
        return ownKey;
    }


    /**
     * 加密 　　
     * 
     * @param key
     *            加密的密钥 　　
     * @param data
     *            待加密的明文数据 　　
     * @return 加密后的数据
     */
    public static byte[] encrypt(Key key, byte[] data) 
    {
        try 
        {
            Cipher cipher = Cipher.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            // 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
            // 加密块大小为127
            // byte,加密后为128个byte;因此共有2个加密块，第一个127
            // byte第二个为1个byte
            int blockSize = cipher.getBlockSize();
            // 获得加密块加密后块大小
            int outputSize = cipher.getOutputSize(data.length);
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) 
            {
                //这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中，
                //而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
                if (data.length - i * blockSize > blockSize)
                {
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i* outputSize);
                }
                else
                {
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
                }
                i++;
            }
            return raw;
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 加密 　　
     * 
     * @param key
     *            加密的密钥 　　
     * @param data
     *            待加密的明文数据 　　
     * @return 加密后的数据
     */
    public static String encrypt(Key key, String strData) 
    {
        byte[] data = strData.getBytes();
        byte[] rtnData = encrypt(key,data);
        return rtnData.toString();
    }

    /**
     * 解密 　　
     * 
     * @param key
     *            解密的密钥 　　
     * @param raw
     *            已经加密的数据 　　
     * @return 解密后的明文 　
     */
    @SuppressWarnings("static-access")
	public static byte[] decrypt(Key key, byte[] raw) 
    {
        try 
        {
            Cipher cipher = Cipher.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(cipher.DECRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
            while (raw.length - j * blockSize > 0) 
            {
                bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } 
        catch (Exception e) 
        {
            return null;
        }
    }
    
    /**
     * 解密 　　
     * 
     * @param key
     *            解密的密钥 　　
     * @param raw
     *            已经加密的数据 　　
     * @return 解密后的明文 　
     */
    public static String decrypt(Key key, String strData) 
    {
        byte[] raw = strData.getBytes();
        byte[] rtnData = decrypt(key,raw);
        return rtnData.toString();
    }
    
    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    @SuppressWarnings("unused")
	private RSAPublicKey getPubKey()
    {       
        return getPubKey("SeenKey.dat");
    }
    
    private RSAPublicKey getPubKey(String strKey)
    {       
        RSAPublicKey pubKey = null;
        try {
            InputStream is = RSAUtil.class.getResourceAsStream(strKey);
            if(is == null)
            {
                System.out.println("通过公钥文件获取密钥KEY失败！");
                return null;
            }
            ObjectInputStream oos = new ObjectInputStream(is);
            pubKey = (RSAPublicKey) oos.readObject();
            oos.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("通过公钥文件获取密钥KEY失败！");
            return null;
        }
        return pubKey;
    }
    
    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    @SuppressWarnings("unused")
	private RSAPrivateKey getOwnKey()
    {       
        return getOwnKey("PriSeenRSAKey.dat");
    }
    
    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    public RSAPrivateKey getOwnKey(String strKey)
    {       
        RSAPrivateKey ownKey = null;
        try {
            InputStream is = RSAUtil.class.getResourceAsStream(strKey);
            ObjectInputStream oos = new ObjectInputStream(is);
            ownKey = (RSAPrivateKey) oos.readObject();
            oos.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("通过私钥文件获取密钥KEY失败！");
            return null;
        }
        return ownKey;
    }
    
    /**
     * 根据加密KEY文件存放路径返回实例
     * @param filePath  路径
     * @return          成功返回实例/失败返回null
     */
    public RSAPrivateKey getOwnKeyForPath(String strKey)
    {       
        RSAPrivateKey ownKey = null;
        try {
            InputStream is = new FileInputStream(new File(strKey));
            ObjectInputStream oos = new ObjectInputStream(is);
            ownKey = (RSAPrivateKey) oos.readObject();
            oos.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
            System.out.println("通过私钥文件获取密钥KEY失败！");
            return null;
        }
        return ownKey;
    }
    
    /**
      * 文件file进行加密并保存目标文件destFile中

      * @param srcFileName
      *            要加密的文件 如c:/test/srcFile.txt
      * @param destFileName
      *            加密后存放的文件名 如c:/加密后文件.txt
      */

    public void encryptFile(String srcFileName,String destFileName,Key key) throws Exception 
    {
        OutputStream outputWriter = null;
        InputStream inputReader = null;
        CipherInputStream cis = null;
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,new org.bouncycastle.jce.provider.BouncyCastleProvider());
            byte[] buf = new byte[1024];
            int bufl;
            cipher.init(Cipher.ENCRYPT_MODE, key);
            outputWriter = new FileOutputStream(destFileName);
            inputReader = new FileInputStream(srcFileName);
            cis = new CipherInputStream(inputReader, cipher);
            while ((bufl = cis.read(buf)) > 0) {
                outputWriter.write(buf, 0, bufl);
            }
            outputWriter.flush();
            outputWriter.close();
            cis.close();
            inputReader.close();
        } 
        catch (Exception e) 
        {
            throw e;
        } 
        finally 
        {
            try 
            {
                if (outputWriter != null)
                {
                    outputWriter.close();
                }
                if (cis != null)
                {
                    cis.close();
                }
                if (inputReader != null)
                {
                    inputReader.close();
                }
            } 
            catch (Exception e) 
            {
            }
        }
    }

    /**
      * 文件file进行加密并保存目标文件destFile中

      * @param srcFileName
      *            已加密的文件 如c:/加密后文件.txt
      * @param destFileName
      *            解密后存放的文件名 如c:/ test/解密后文件.txt
      */

    public void decryptFile(String srcFileName,String destFileName,Key key) throws Exception 
    {
        OutputStream outputWriter = null;
        InputStream inputReader = null;
        CipherOutputStream cos = null;
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,new org.bouncycastle.jce.provider.BouncyCastleProvider());
            byte[] buf = new byte[1024];
            int bufl;
            cipher.init(Cipher.DECRYPT_MODE, key);
            outputWriter = new FileOutputStream(destFileName);
            inputReader = new FileInputStream(srcFileName);
            cos = new CipherOutputStream(outputWriter, cipher);
            while ((bufl = inputReader.read(buf)) >= 0) 
            {
                cos.write(buf, 0, bufl);
            }
            cos.flush();
            outputWriter.flush();
            outputWriter.close();
            cos.close();
            inputReader.close();
        } 
        catch (Exception e) 
        {
            throw e;
        } 
        finally 
        {
            try {
                if (outputWriter != null) 
                {
                    outputWriter.close();
                }
                if (cos != null) 
                {
                    cos.close();
                }
                if (inputReader != null) {
                    inputReader.close();
                }
            } 
            catch (Exception e) 
            {
            }
        }
    }    
    /**
     * 用来生成License
     * Caolj 2018-03-29
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception 
    {
        //密钥生成只能一次，不可再执行
    	//RSAUtil.getInstance().createRSAKeys("D:/", "NewheroKey.dat");
        Key key = RSAUtil.getInstance().getOwnKey("PriNewheroKey.dat");
        //License格式：系统名称;MAC地址(多个,隔开);有效期(20170705,不限制使用unlimited);部门数;用户数;系统权限模式(0:三员分立，1：市级三员分立不设立审计员，2：不设立独立三员)
        //系统名称分为三类：NEWHERO，TEST，TESTDEV
        String enStr = "NEWHEROTEST;D4-81-D7-8D-36-43;20180930;5;10;2";
        byte[] rew = RSAUtil.encrypt(key, enStr.getBytes());
        File file = new File("D:/license.newhero");
        OutputStream out = new FileOutputStream(file);
        out.write(rew);
        out.close();
        
    }
}
